﻿using System;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Windows.Forms;
using Client.Interfaces;
using Global.Impl.Client;
using Global.Interfaces.Client;
using Global.Interfaces.Server;
using System.Security.Cryptography.X509Certificates;
using System.IO;
using System.Threading;
using Interfaces.Impl;
using System.Configuration;
using System.Diagnostics;

namespace Client.Controllers
{
    internal class ChatController : IDisposable, IChatController
    {
        #region Variables
        private string _endpoint;
        private string _password;
        private IBuddy _buddy;
        private IMainController _mainController;
        private IChatView _view;
        private DuplexChannelFactory<IChatChannel> _factory;
        private IChatChannel _participant;
        private IChat _chat;
        private bool _logReceived;
        private readonly object _lockObject = new object();
        private bool _filenameReceived;
        private string _fileFromUsername;
        private string _fileFromFileName;
        private Thread _fileReceiver;
        private Stream _fileStream;
        private bool _fileReceived;
        #endregion

        internal ChatController(IMainController mainController, IChatView view, string endpoint, string password, IBuddy buddy)
        {
            _view = view;
            _mainController = mainController;
            _endpoint = endpoint;
            _buddy = buddy;
            _logReceived = false;
            _filenameReceived = false;
            _fileReceived = false;

            _password = password;
        }

        #region Chat events
        void participant_JoinChat(string username)
        {
            _view.Log += username + "(" + DateTime.Now.ToShortTimeString() + "), joined chat" + Environment.NewLine;
            if (username != _mainController.Username)
                _participant.SendLog(_chat.Log);
        }

        void participant_LeaveChat(string username)
        {
            if (username != _mainController.Username)
                _view.Log += username + ", left chat (" + DateTime.Now.ToShortTimeString() + ")" + Environment.NewLine;
        }

        void participant_NewMessage(string username, string message)
        {
            _view.Log += username + " (" + DateTime.Now.ToShortTimeString() + "): " + message + Environment.NewLine;
        }

        void _chat_LogRecieved(string log)
        {
            if (!_logReceived)
            {
                _view.Log = log;
                _logReceived = true;
            }
        }

        void _chat_FileReceived(Stream fromStream)
        {
            if (FilenameReceived && _fileFromUsername == _mainController.Username)
            {
                FilenameReceived = false;
                FileReceived = false;
                return;
            }

            MemoryStream ms = new MemoryStream(ReadFully(fromStream));
            ms.Position = 0;
            FileInMemory = ms;
            fromStream.Close();

            _fileReceiver = new Thread(new ThreadStart(ReceiveFile));
            _fileReceiver.Start();
            FileReceived = true;
        }

        void _chat_FileNameReceived(string username, string filename)
        {
            _fileFromUsername = username;
            _fileFromFileName = filename;
            FilenameReceived = true;
        }

        private bool FilenameReceived
        {
            get
            {
                bool tmp;
                lock (_lockObject)
                {
                    tmp = _filenameReceived;
                }
                return tmp;
            }
            set
            {
                lock (_lockObject)
                {
                    _filenameReceived = value;
                }
            }
        }

        private bool FileReceived
        {
            get
            {
                bool tmp;
                lock (_lockObject)
                {
                    tmp = _fileReceived;
                }
                return tmp;
            }
            set
            {
                lock (_lockObject)
                {
                    _fileReceived = value;
                }
            }
        }

        private Stream FileInMemory
        {
            get
            {
                Stream tmp;
                lock (_lockObject)
                {
                    tmp = _fileStream;
                }
                return tmp;
            }
            set
            {
                lock (_lockObject)
                {
                    _fileStream = value;
                }
            }
        }
        #endregion

        #region FileReceiver
        private void ReceiveFile()
        {
            DateTime timeOfRequest = DateTime.Now;
            while (!FilenameReceived && !FileReceived && timeOfRequest.AddSeconds(20) >= DateTime.Now)
                Thread.Sleep(1000);

            if (FilenameReceived && FileReceived)
            {
                _view.Log += _fileFromUsername + ", sent a file: " + _fileFromFileName + Environment.NewLine;
                _view.FileReceived(FileInMemory, _fileFromUsername, _fileFromFileName);
            }

            FilenameReceived = false;
            FileReceived = false;
        }

        private static byte[] ReadFully(Stream stream)
        {
            byte[] buffer = new byte[32768];
            using (MemoryStream ms = new MemoryStream())
            {
                while (true)
                {
                    int read = stream.Read(buffer, 0, buffer.Length);
                    if (read <= 0)
                        return ms.ToArray();
                    ms.Write(buffer, 0, read);
                }
            }
        }
        #endregion

        #region IChatController Members

        public void InitChat()
        {
            _view.Log = "Connecting - Please wait..." + Environment.NewLine;

            NetPeerTcpBinding binding;
            EndpointAddress p2pAddress;
            ServiceEndpoint serviceEndpoint;
            InstanceContext instanceContext;

            // Setup binding
            string resolverAddress = ConfigurationManager.AppSettings["resolverAddress"];

            NetTcpBinding resolverBinding = new NetTcpBinding();
            resolverBinding.Security.Transport.ClientCredentialType = TcpClientCredentialType.None;
            resolverBinding.Security.Transport.ProtectionLevel = System.Net.Security.ProtectionLevel.EncryptAndSign;
            resolverBinding.Security.Mode = SecurityMode.Transport;

            binding = new NetPeerTcpBinding();
            binding.Resolver.Mode = System.ServiceModel.PeerResolvers.PeerResolverMode.Custom;
            binding.Resolver.Custom.Address = new EndpointAddress(resolverAddress);
            binding.Resolver.Custom.Binding = new NetTcpBinding(SecurityMode.None); //resolverBinding;
            //binding.Security.Mode = SecurityMode.Transport;
            //binding.Security.Transport.CredentialType = PeerTransportCredentialType.Certificate;
                
            // Setup endpoint
            p2pAddress = new EndpointAddress("net.p2p://" + _endpoint);

            // Setup service endpoint
            ContractDescription contractDesc = ContractDescription.GetContract(
                    typeof(IChat),
                    typeof(Chat.Chat));

            serviceEndpoint = new ServiceEndpoint(
                contractDesc,
                binding,
                p2pAddress);

            // Create IChat instance
            _chat = new Chat.Chat();

            // Create instance context
            instanceContext = new InstanceContext(_chat);
            _factory = new DuplexChannelFactory<IChatChannel>(instanceContext, serviceEndpoint);
            _factory.Credentials.Peer.MeshPassword = _password;
            _factory.Credentials.Peer.PeerAuthentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None;
            _factory.Credentials.Peer.SetCertificate("CN=sMessenger", StoreLocation.CurrentUser, StoreName.TrustedPublisher);
            _factory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None;
            _participant = _factory.CreateChannel();

            _participant.BeginOpen(new TimeSpan(0, 0, 30), OpenEnded, null);

            _chat.NewMessage += new EventHandlers.NewMessageHandler(participant_NewMessage);
            _chat.LeaveChat += new EventHandlers.LeaveChatHandler(participant_LeaveChat);
            _chat.JoinChat += new EventHandlers.JoinChatHandler(participant_JoinChat);
            _chat.LogReceived += new EventHandlers.LogHandler(_chat_LogRecieved);
            _chat.FileReceived += new EventHandlers.FileHandler(_chat_FileReceived);
            _chat.FileNameReceived += new EventHandlers.FileNameHandler(_chat_FileNameReceived);

            // Call buddy directly
            if (_buddy != null)
                InvitationInitiator.InviteBuddy(_mainController, _buddy, _endpoint, _password);
        }

        private void OpenEnded(IAsyncResult result)
        {
            try
            {
                _participant.EndOpen(result);
            }
            catch (Exception cError)
            {
                //MessageBox.Show("Could not connect to chat: " + Environment.NewLine + cError.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                Debug.WriteLine("Could not connect to chat: " + Environment.NewLine + cError.ToString());
                _participant = null;
                _factory = null;
                _view.CloseWindow();
                return;
            }

            _view.Log = "Joined chat" + Environment.NewLine;
            _view.Ready();
            _participant.Join(_mainController.Username);

            _view.Title = "sMessenger chat, GUID: " + _endpoint;
        }

        public void Send(string msg)
        {
            _participant.Send(_mainController.Username, msg);
        }

        public void Leave()
        {
            if (_participant != null)
            {
                _participant.Leave(_mainController.Username);
                _participant.Close();
            }
            if (_factory != null)
            {
                _factory.Close();
            }
            _mainController.LeftChat(_endpoint);
        }

        public void SendFile(string file)
        {
            _view.Log += "Sending file: " + file.Substring(file.LastIndexOf("\\") + 1) + Environment.NewLine;
            using (MemoryStream stream = new MemoryStream(File.ReadAllBytes(file), false))
            {
                stream.Position = 0;
                _participant.SendFileName(_mainController.Username, file.Substring(file.LastIndexOf("\\") + 1));
                _participant.SendFile(stream);
            }
        }

        public void NodeDropped(object o)
        {
            TreeNode node = o as TreeNode;
            if (node != null)
            {
                IBuddy b = node.Tag as IBuddy;
                if (b != null)
                {
                    _view.Log += "Inviting user: " + b.Username + Environment.NewLine;
                    InvitationInitiator.InviteBuddy(_mainController, b, _endpoint, _password);
                }
            }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            if (_participant != null)
            {
                _participant.Leave(_mainController.Username);
                _participant.Close();
            }
            if (_factory != null)
                _factory.Close();
        }

        #endregion
    }
}
